# -*- coding: utf-8 -*-
import asyncio
import logging
import hashlib
import time
from typing import *

import aiohttp
import yarl

from . import ws_base
from .. import utils

__all__ = (
    'BLiveClient',
)

logger = logging.getLogger('blivedm')

UID_INIT_URL = 'https://api.bilibili.com/x/web-interface/nav'
BUVID_INIT_URL = 'https://www.bilibili.com/'
ROOM_INIT_URL = 'https://api.live.bilibili.com/room/v1/Room/get_info'
DANMAKU_SERVER_CONF_URL = 'https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo'
LIVER_INFO_URL = "https://api.bilibili.com/x/space/wbi/acc/info?mid="
SEND_DANMU_URL = "https://api.live.bilibili.com/msg/send"
DEFAULT_DANMAKU_SERVER_LIST = [
    {'host': 'broadcastlv.chat.bilibili.com', 'port': 2243, 'wss_port': 443, 'ws_port': 2244}
]

# DEFAULT_DANMAKU_SERVER_LIST = [
#     {'host': 'zj-cn-live-comet.chat.bilibili.com', 'port': 2243, 'wss_port': 2245, 'ws_port': 2244}
# ]

# TEST_TOKEN = "uPDHra4vP4f8Azbxz8FR3xnieBXT6hE3Rwk54JBq-AjG6XMuJyi0nMhECfBcrS0pM3BZn0EPcAHlI6YLtGLc6m2kdZrKCxroznBICSGcRqOHE32BmyL1PX3y5QqMEHh7loERgZv52ysMQOY3X86fpKWdAwp1Dp1FVhl3Vb-oWhlqaqhvEdUcGR3lyu4="

class BLiveClient(ws_base.WebSocketClientBase):
    """
    web端客户端

    :param room_id: URL中的房间ID，可以用短ID
    :param uid: B站用户ID，0表示未登录，None表示自动获取
    :param session: cookie、连接池
    :param heartbeat_interval: 发送心跳包的间隔时间（秒）
    """

    def __init__(
        self,
        room_id: int,
        *,
        name: Optional[str] = None,
        uid: Optional[int] = None,
        session: Optional[aiohttp.ClientSession] = None,
        heartbeat_interval=30,
    ):
        super().__init__(session, heartbeat_interval)

        self._tmp_room_id = room_id
        """用来init_room的临时房间ID，可以用短ID"""
        self._uid = uid

        self._name =name

        # 在调用init_room后初始化的字段
        self._room_owner_uid: Optional[int] = None
        """主播用户ID"""
        self._host_server_list: Optional[List[dict]] = None
        """
        弹幕服务器列表

        `[{host: "tx-bj4-live-comet-04.chat.bilibili.com", port: 2243, wss_port: 443, ws_port: 2244}, ...]`
        """
        self._host_server_token: Optional[str] = None
        """连接弹幕服务器用的token"""

    @property
    def tmp_room_id(self) -> int:
        """
        构造时传进来的room_id参数
        """
        return self._tmp_room_id

    @property
    def room_owner_uid(self) -> Optional[int]:
        """
        主播用户ID，调用init_room后初始化
        """
        return self._room_owner_uid

    @property
    def uid(self) -> Optional[int]:
        """
        当前登录的用户ID，未登录则为0，调用init_room后初始化
        """
        return self._uid

    @property
    def original_room_id(self) -> Optional[int]:
        """临时房间ID"""
        return self._tmp_room_id
    
    @property
    def name(self) -> Optional[str]:
        """主播名字，可选参数"""
        if self._name == None:
            return self.original_room_id
        else:
            return self._name

    async def init_room(self):
        """
        初始化连接房间需要的字段

        :return: True代表没有降级，如果需要降级后还可用，重载这个函数返回True
        """
        if self._uid is None:
            if not await self._init_uid():
                logger.warning('room=%d _init_uid() failed', self._tmp_room_id)
                self._uid = 0

        if self._get_buvid() == '':
            if not await self._init_buvid():
                logger.warning('room=%d _init_buvid() failed', self._tmp_room_id)

        res = True
        if not await self._init_room_id_and_owner():
            res = False
            # 失败了则降级
            self._room_id = self._tmp_room_id
            self._room_owner_uid = 0

        if not await self._init_host_server():
            res = False
            # 失败了则降级
            self._host_server_list = DEFAULT_DANMAKU_SERVER_LIST
            self._host_server_token = None
        return res

    async def _init_uid(self):
        cookies = self._session.cookie_jar.filter_cookies(yarl.URL(UID_INIT_URL))
        sessdata_cookie = cookies.get('SESSDATA', None)
        if sessdata_cookie is None or sessdata_cookie.value == '':
            # cookie都没有，不用请求了
            self._uid = 0
            return True

        try:
            async with self._session.get(
                UID_INIT_URL,
                headers={'User-Agent': utils.USER_AGENT},
            ) as res:
                if res.status != 200:
                    logger.warning('room=%d _init_uid() failed, status=%d, reason=%s', self._tmp_room_id,
                                   res.status, res.reason)
                    return False
                data = await res.json()
                if data['code'] != 0:
                    if data['code'] == -101:
                        # 未登录
                        self._uid = 0
                        return True
                    logger.warning('room=%d _init_uid() failed, message=%s', self._tmp_room_id,
                                   data['message'])
                    return False

                data = data['data']
                if not data['isLogin']:
                    # 未登录
                    self._uid = 0
                else:
                    self._uid = data['mid']
                return True
        except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
            logger.exception('room=%d _init_uid() failed:', self._tmp_room_id)
            return False

    def _get_buvid(self):
        cookies = self._session.cookie_jar.filter_cookies(yarl.URL(BUVID_INIT_URL))
        buvid_cookie = cookies.get('buvid3', None)
        if buvid_cookie is None:
            return ''
        return buvid_cookie.value

    async def _init_buvid(self):
        try:
            async with self._session.get(
                BUVID_INIT_URL,
                headers={'User-Agent': utils.USER_AGENT},
            ) as res:
                if res.status != 200:
                    logger.warning('room=%d _init_buvid() status error, status=%d, reason=%s',
                                   self._tmp_room_id, res.status, res.reason)
        except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
            logger.exception('room=%d _init_buvid() exception:', self._tmp_room_id)
        return self._get_buvid() != ''

    async def _init_room_id_and_owner(self):
        try:
            async with self._session.get(
                ROOM_INIT_URL,
                headers={'User-Agent': utils.USER_AGENT},
                params={
                    'room_id': self._tmp_room_id
                },
            ) as res:
                if res.status != 200:
                    logger.warning('room=%d _init_room_id_and_owner() failed, status=%d, reason=%s', self._tmp_room_id,
                                   res.status, res.reason)
                    return False
                data = await res.json()
                if data['code'] != 0:
                    logger.warning('room=%d _init_room_id_and_owner() failed, message=%s', self._tmp_room_id,
                                   data['message'])
                    return False
                if not self._parse_room_init(data['data']):
                    return False
        except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
            logger.exception('room=%d _init_room_id_and_owner() failed:', self._tmp_room_id)
            return False
        return True

    def _parse_room_init(self, data):
        self._room_id = data['room_id']
        self._room_owner_uid = data['uid']
        #print(data)
        return True

    def _md5_encrypt(self, text):
        # 创建md5对象
        md5 = hashlib.md5()
        # 需要加密的字符串必须编码为bytes
        md5.update(text.encode('utf-8'))
        # 返回16进制格式的哈希值
        return md5.hexdigest()

    def _get_wrid_danmuInfo(self, room_id):
        a = "ea1db124af3c7062474693fa704f4ff8"
        _type = "0"
        web_location = "444.8"
        wts = int(time.time())
        f = f"id={room_id}&type={_type}&web_location={web_location}&wts={wts}"
        w_rid = self._md5_encrypt(f + a)
        return w_rid, wts

    def _get_wrid_send_gift(self):
        a = "ea1db124af3c7062474693fa704f4ff8"
        web_location = "444.8"
        wts = int(time.time())
        f = f"web_location={web_location}&wts={wts}"
        w_rid = self._md5_encrypt(f + a)
        return w_rid, wts

    async def send_danmu(self, messages:List[str], reply_uid: int = 0):
        for message in messages:
            datas = {
                'bubble': '0',
                'msg': message,
                'color': '16777215',
                'mode': '1',
                'room_type': '0',
                'jumpfrom': '86002',
                'reply_mid': str(reply_uid),
                'reply_attr': '0',
                'replay_dmid': '',
                'statistics': '{"appId":100,"platform":5}',
                'reply_type': '0',
                'reply_uname': '',
                'fontsize': '25',
                'rnd': '1749840535',
                'roomid': self.room_id,
                'csrf': '6251c1cf2fb499a1e1fdaea523e0d3fb',
                'csrf_token': '6251c1cf2fb499a1e1fdaea523e0d3fb'
            }

            headers = {
                "Cookie": "buvid3=5914BEA2-71F3-F6A3-B663-991BBCA5D70287915infoc; b_nut=1750158487; b_lsid=B557D35C_1977D9335FC; _uuid=C224106ED-1985-EFFF-82107-74655A6A4105C89088infoc; buvid_fp=c416f2989e32c68464c3cad5cbb33243; buvid4=6560F025-5D6E-8D9C-B5DE-4CBDE345562D89205-025061719-kTUICjB%2Fpq558LqHSGWHVg%3D%3D; header_theme_version=OPEN; enable_web_push=DISABLE; enable_feed_channel=ENABLE; home_feed_column=5; browser_resolution=1920-952; SESSDATA=e3fd2266%2C1765710583%2Ccd77d%2A62CjDQ76OMeKb6a88VIhYvyer1hhjMvmVB4J9Ze7R8LJceYglHuMWWzmiwjImcu5TGNOUSVnFLOFF6OHYwNmhjU2dmOEpGcHlJS2N3eFNWbm1EbDRQYnJGeHFtV2xrck85TlE0S1d4MmtfVnhDUkRDMFRKeXdrSmJjQTlHVXJPWENncW5rcldkbXF3IIEC; bili_jct=6251c1cf2fb499a1e1fdaea523e0d3fb; DedeUserID=3546811172129048; DedeUserID__ckMd5=72dcb533a8ace483; sid=df07cuo4; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTA0MTc3OTAsImlhdCI6MTc1MDE1ODUzMCwicGx0IjotMX0.MhyhYVJxuUpQoH2DwmmbhcYpPtyPUmK76odcouTfTjw; bili_ticket_expires=1750417730; theme-tip-show=SHOWED",
                "Origin": "https://live.bilibili.com",
                "Referer": "https://live.bilibili.com/192?live_from=82002&spm_id_from=333.1007.top_right_bar_window_dynamic.content.click",
                "Sec-Ch-Ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
                "Sec-Ch-Ua-Mobile": "?0",
                "Sec-Ch-Ua-Platform": "\"Windows\"",
                "Sec-Fetch-Dest": "empty",
                "Sec-Fetch-Mode": "cors",
                "Sec-Fetch-Site": "same-site",
                'User-Agent': "Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 80.0.3987.122  Safari / 537.36"
            }

            w_rid, wts = self._get_wrid_send_gift()

            params = {
                'web_location': '444.8',
                "w_rid": w_rid,
                "wts": wts
            }

            try:
                async with self._session.post(
                    SEND_DANMU_URL,
                    headers=headers,
                    params=params,
                    data=datas,
                ) as res:
                    if res.status != 200:
                        logger.warning('room=%d _send_danmu() failed', self._room_id)
            except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
                logger.exception('room=%d _send_danmu() failed because of network problem', self._room_id)
            
            await asyncio.sleep(1)


    async def _init_host_server(self):
        try:
            w_rid, wts = self._get_wrid_danmuInfo(self._room_id)
            async with self._session.get(
                DANMAKU_SERVER_CONF_URL,
                headers={'User-Agent': utils.USER_AGENT},
                params={
                    'id': self._room_id,
                    'type': 0,
                    'web_location': "444.8",
                    'w_rid':w_rid,
                    'wts': wts
                },
            ) as res:
                if res.status != 200:
                    logger.warning('room=%d _init_host_server() failed, status=%d, reason=%s', self._room_id,
                                   res.status, res.reason)
                    return False
                data = await res.json()
                if data['code'] != 0:
                    logger.warning('room=%d _init_host_server() failed, message=%s', self._room_id, data['message'])
                    return False
                if not self._parse_danmaku_server_conf(data['data']):
                    return False
        except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
            logger.exception('room=%d _init_host_server() failed:', self._room_id)
            return False
        return True

    def _parse_danmaku_server_conf(self, data):
        self._host_server_list = data['host_list']
        # print("host_server_list:")
        # print(self._host_server_list)
        self._host_server_token = data['token']
        # print(self._host_server_token)
        if not self._host_server_list:
            logger.warning('room=%d _parse_danmaku_server_conf() failed: host_server_list is empty', self._room_id)
            return False
        return True

    async def _on_before_ws_connect(self, retry_count):
        """
        在每次建立连接之前调用，可以用来初始化房间
        """
        # 重连次数太多则重新init_room，保险
        reinit_period = max(3, len(self._host_server_list or ()))
        if retry_count > 0 and retry_count % reinit_period == 0:
            self._need_init_room = True
        await super()._on_before_ws_connect(retry_count)

    def _get_ws_url(self, retry_count) -> str:
        """
        返回WebSocket连接的URL，可以在这里做故障转移和负载均衡
        """
        host_server = self._host_server_list[retry_count % len(self._host_server_list)]

        # print(f"wss://{host_server['host']}:{host_server['wss_port']}/sub")

        return f"wss://{host_server['host']}:{host_server['wss_port']}/sub"

    async def _send_auth(self):
        """
        发送认证包
        """
        auth_params = {
            'uid': self._uid,
            'roomid': self._room_id,
            'protover': 3,
            'platform': 'web',
            'type': 2,
            'buvid': self._get_buvid(),
        }
        if self._host_server_token is not None:
            auth_params['key'] = self._host_server_token
        await self._websocket.send_bytes(self._make_packet(auth_params, ws_base.Operation.AUTH))
